package ph.com.gs3.formalistics.presenter.fragments.workers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.json.JSONException;
import org.json.JSONObject;

import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;
import ph.com.gs3.formalistics.model.communicators.DocumentCommunicatorModule;
import ph.com.gs3.formalistics.model.communicators.HttpCommunicator;
import ph.com.gs3.formalistics.model.dao.DocumentsDAO;
import ph.com.gs3.formalistics.model.dao.FormsDAO;
import ph.com.gs3.formalistics.model.dao.OutgoingDocumentsDAO;
import ph.com.gs3.formalistics.model.dao.OutgoingStarMarksDAO;
import ph.com.gs3.formalistics.model.dao.UserAccountsDAO;
import ph.com.gs3.formalistics.model.valueobjects.business.User;
import ph.com.gs3.formalistics.model.valueobjects.business.document.Document;
import ph.com.gs3.formalistics.model.valueobjects.business.document.OutgoingDocument;
import ph.com.gs3.formalistics.model.valueobjects.business.document.Document.DocumentStar;
import ph.com.gs3.formalistics.model.valueobjects.business.form.Form;
import ph.com.gs3.formalistics.model.valueobjects.business.form.FormStub;
import ph.com.gs3.formalistics.model.valueobjects.business.form.FormField.InvalidFormFieldException;
import ph.com.gs3.formalistics.presenter.MainActivity;
import ph.com.gs3.formalistics.service.managers.DocumentManager;
import ph.com.gs3.formalistics.service.managers.DocumentManager.DocumentSaveFailedException;
import ph.com.gs3.formalistics.view.valueobjects.UserActionListViewItem;
import ph.com.gs3.formalistics.view.valueobjects.parsers.DocumentParser;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;

public class MainWorkerFragment extends Fragment {

	public static final String TAG = MainWorkerFragment.class.getSimpleName();

	// Data Access Objects
	private FormsDAO formsDAO;
	private DocumentsDAO documentsDAO;
	private OutgoingDocumentsDAO outgoingDocumentsDAO;
	private OutgoingStarMarksDAO outgoingStarMarksDAO;
	private UserAccountsDAO userAccountsDAO;

	private DocumentCommunicatorModule documentCommunicatorModule;

	// Managers
	private DocumentManager documentManager;

	// Fields
	private User currentlyActiveUser;

	private Map<String, Form> formCache;

	public static enum FormSearchMode {
		NO_CACHE, CACHED
	};

	public static MainWorkerFragment createInstance() {
		MainWorkerFragment instance = new MainWorkerFragment();

		instance.formCache = new HashMap<>();

		return instance;
	}

	// required empty parameter constructor
	public MainWorkerFragment() {}

	private void initializeDependencies() {

		// TODO: decide later if these dependencies must be injected to this class, a
		// createInstance method is already provided, add injection there
		formsDAO = new FormsDAO(getActivity());
		documentsDAO = new DocumentsDAO(getActivity());
		outgoingDocumentsDAO = new OutgoingDocumentsDAO(getActivity());
		outgoingStarMarksDAO = new OutgoingStarMarksDAO(getActivity());
		userAccountsDAO = new UserAccountsDAO(getActivity());

		documentCommunicatorModule = new DocumentCommunicatorModule(new HttpCommunicator(),
		        currentlyActiveUser.getAccountServer());

		/*
		 * Note: since this manager does not have a communicator module, it cannot do
		 * network related operations.
		 */
		documentManager = new DocumentManager(formsDAO, documentsDAO, outgoingDocumentsDAO,
		        outgoingStarMarksDAO, documentCommunicatorModule);

	}

	// ================================================================================
	// {{ Document Related Methods

	public int getAvailableDocumentCount(String server) {

		int count = 0;

		documentsDAO.open();
		try {
			count = (int) documentsDAO.getDocumentsCount(server);
		} finally {
			documentsDAO.close();
		}

		return count;

	}

	public Document getDocument(String webId, String formWebId, String server) throws JSONException {

		Document document = null;
		documentsDAO.open();
		try {
			FLLogger.d(TAG, webId + " " + formWebId + " " + server);
			document = documentsDAO.getDocument(webId, formWebId, server);
		} finally {
			documentsDAO.close();
		}

		return document;
	}

	public List<Document> getAllDocuments() {

		List<String> availableForms = getAvailableFormsWebIds();

		List<Document> documents;

		String server = currentlyActiveUser.getAccountServer();
		String currentUserId = Integer.toString(currentlyActiveUser.getId());

		if (availableForms.size() <= 0) {
			// there are no documents to fetch since there are no forms yet, return an
			// empty list
			documents = new ArrayList<>();
		} else {
			documentsDAO.open();
			documents = documentsDAO.getAllDocuments(server, currentUserId, availableForms);
			documentsDAO.close();
		}

		return documents;

	}

	public List<Document> getAllDocumentsByForm(String formWebId) {

		List<Document> documents;
		List<String> forms = new ArrayList<>();

		String server = currentlyActiveUser.getAccountServer();
		String currentUserId = Integer.toString(currentlyActiveUser.getId());

		forms.add(formWebId);

		documentsDAO.open();
		documents = documentsDAO.getAllDocuments(server, currentUserId, forms);
		documentsDAO.close();

		return documents;
	}

	public List<OutgoingDocument> getAllOutgoingDocumentActions(String currentUserDbId) {

		outgoingDocumentsDAO.open();
		List<OutgoingDocument> outgoingActions = outgoingDocumentsDAO
		        .getAllOutgoingDocument(currentUserDbId);
		outgoingDocumentsDAO.close();

		return outgoingActions;

	}

	public List<Document> getAllDocumentsWithOutgoingComments(String currentUserDbId, String server) {

		documentsDAO.open();
		List<Document> documents = documentsDAO.getDocumentsWithOutgoingComment(server);
		documentsDAO.close();

		return documents;

	}

	public void markDocumentStar(String formWebId, String documentWebId, String documentDbId,
	        DocumentStar starMark) {

		String userId = Integer.toString(currentlyActiveUser.getId());
		documentManager.saveDocumentStarAsOutgoing(formWebId, documentWebId, documentDbId, userId,
		        starMark);

	}

	// }}

	// ================================================================================
	// {{ Form Related Methods

	public void saveDocumentAction(Document document, String action)
	        throws DocumentSaveFailedException {

		Form documentForm = getForm(document.getServer(), document.getFormWebId());

		String formName = documentForm.getName();
		String currentUserId = Integer.toString(currentlyActiveUser.getId());

		// throws DocumentSaveFailedException
		documentManager.addDocumentToOutgoingDocuments(document, formName, action, currentUserId);

	}

	public List<String> getAvailableFormsWebIds() {

		String server = currentlyActiveUser.getAccountServer();
		String companyId = currentlyActiveUser.getCompanyId();

		formsDAO.open();
		List<String> availableForms = formsDAO.getAllCompanyFormWebIds(server, companyId);
		formsDAO.close();

		return availableForms;

	}

	public List<FormStub> getAvailableForms() {

		String server = currentlyActiveUser.getAccountServer();
		String companyId = currentlyActiveUser.getCompanyId();

		formsDAO.open();
		List<FormStub> availableForms = formsDAO.getAllCompanyFormStubs(server, companyId);
		formsDAO.close();

		return availableForms;

	}

	/**
	 * Searches for forms on cached mode
	 * 
	 * @param server
	 * @param webId
	 * @return
	 */
	public Form getForm(String server, String webId) {

		return getForm(server, webId, FormSearchMode.CACHED);

	}

	public Form getForm(String server, String webId, FormSearchMode searchMode) {

		Form form = null;

		if (formCache.containsKey(webId) && searchMode == FormSearchMode.CACHED) {
			form = formCache.get(webId);
		} else {
			formsDAO.open();
			try {
				form = formsDAO.getForm(webId, server);
			} catch (JSONException | InvalidFormFieldException e) {
				e.printStackTrace();
			} finally {
				formsDAO.close();
			}

			// add the form to the cache
			formCache.put(webId, form);
		}

		return form;
	}

	// }}

	// ================================================================================
	// {{ User Related Methods

	public User getCurrentlyActiveUser() {

		if (currentlyActiveUser == null || currentlyActiveUser.getId() <= 0) {
			userAccountsDAO.open();
			currentlyActiveUser = userAccountsDAO.getActiveUserAccount();
			userAccountsDAO.close();
		}

		return currentlyActiveUser;

	}

	public User getUser(String webId, String server) {

		// TODO: add cache capabilities here

		userAccountsDAO.open();
		User user = userAccountsDAO.getUserWithWebIdAndServer(Integer.parseInt(webId), server);
		userAccountsDAO.close();

		return user;

	}

	// }}

	public void logUserOut() {

		userAccountsDAO.open();
		userAccountsDAO.deactivateAllUsers();
		userAccountsDAO.close();

	}

	public UserActionListViewItem generateUserActionListItemFromDocument(Document document,
	        User currentlyActiveUser) {

		UserActionListViewItem userListActionListItem = null;

		int authorWebId = Integer.parseInt(document.getRequestorWebId());
		String server = currentlyActiveUser.getAccountServer();
		String formWebId = document.getFormWebId();

		userAccountsDAO.open();
		User documentAuthor = userAccountsDAO.getUserWithWebIdAndServer(authorWebId, server);
		userAccountsDAO.close();

		Form documentForm = getForm(server, formWebId);

		userListActionListItem = DocumentParser.toUserActionListItem(document, documentForm,
		        documentAuthor, currentlyActiveUser);

		return userListActionListItem;

	}

	public UserActionListViewItem generateUserActionListItemFromDocumentJSONString(
	        String documentJSONString, String formWebId, User currentlyActiveUser) {

		try {
			JSONObject documentRawJSON = new JSONObject(documentJSONString);
			// throws JSONException
			Document document = Document.createDocumentFromJSON(documentRawJSON,
			        currentlyActiveUser.getAccountServer(), formWebId);

			return generateUserActionListItemFromDocument(document, currentlyActiveUser);
		} catch (JSONException e) {
			// this is not likely to happen as the json string is qualified already
			e.printStackTrace();
		}

		return null;

	}

	public void initializeCurrentUser() {
		Bundle extras = getActivity().getIntent().getExtras();

		currentlyActiveUser = null;

		// try checking if a login activity was opened and a user was passed to this
		// activity
		if (extras != null) {
			currentlyActiveUser = (User) extras.getSerializable(MainActivity.EXTRA_LOGGED_IN_USER);
		}

		if (currentlyActiveUser == null) {

			FLLogger.d(TAG, "Main activity opened without active user.");
			throw new IllegalStateException("Main activity opened without active user.");

		}
	}

	// ========================================================================
	// Implementation Methods

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		FLLogger.d(TAG, "on create");

	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);

		FLLogger.d(TAG, "on attach");
		initializeCurrentUser();
		initializeDependencies();
		// register the activity as the mode change listener
		// try {
		//
		// } catch (ClassCastException e) {
		// throw new ClassCastException(activity.toString() +
		// " must implement ModeChangeListener");
		// }

	}

}
